package com.example.fastjsontest.advice;

/**
 * Created by Administrator on 2019/11/9.
 */
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.example.fastjsontest.annotation.ActiveFastJsonProfileInController;
import com.example.fastjsontest.annotation.FastJsonFieldProfile;
import com.example.fastjsontest.annotation.FastJsonFieldProfileType;
import com.example.fastjsontest.init.VoProfileRegistry;
import com.example.fastjsontest.message.CommonMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;


@ControllerAdvice
@Slf4j
public class ResponseAdvisor  implements ResponseBodyAdvice<CommonMessage<Object>>,ApplicationContextAware {


    private ApplicationContext applicationContext;

    /**
     * 判断支持的类型
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        Class<?> parameterType = returnType.getParameterType();
        if (parameterType.isAssignableFrom(CommonMessage.class)) {
            return true;
        }
        return false;
    }

    /**
     * 过滤
     *
     */
    @Override
    public CommonMessage<Object> beforeBodyWrite(CommonMessage<Object> body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
                                  ServerHttpResponse response) {

        String requestPath = request.getURI().getPath();
        log.info("path:{}",requestPath);
        VoProfileRegistry voProfileRegistry = applicationContext.getBean(VoProfileRegistry.class);
        ConcurrentHashMap<String, ActiveFastJsonProfileInController> hashmap = voProfileRegistry.getHashmap();
        ActiveFastJsonProfileInController activeFastJsonProfileInControllerAnnotation = hashmap.get(requestPath);
        if (activeFastJsonProfileInControllerAnnotation == null) {
            log.info("no matched json profile,skip");
            return body;
        }

        String activeFastJsonProfileInController = activeFastJsonProfileInControllerAnnotation.profile();

        //获取clazz全部的字段集合
        Class<?> clazz = activeFastJsonProfileInControllerAnnotation.clazz();


        /**
         * 1:获取指定class中，被指定的profile激活的field集合;如果没有指定，则相当于：全部字段都要序列化
         */
        Set<String> includedFieldSet = getFieldsShouldInclude(activeFastJsonProfileInController, clazz);
        if (CollectionUtils.isEmpty(includedFieldSet)) {
            //如果没有指定要序列化的集合，则相当于为：全部字段
            includedFieldSet = Arrays.stream(clazz.getDeclaredFields()).map(Field::getName).collect(Collectors.toSet());
        }

        /**
         * 2:获取要排除的字段
         */
        Set<String> excludedFieldSet = getFieldsShouldExclude(activeFastJsonProfileInController, clazz);

        /**
         * 3:获取要保留的字段集合，通过集合操作：要序列化的集合--不需序列化的字段集合。
         */
        includedFieldSet.removeAll(excludedFieldSet);


        /**
         * 4:根据第三步的字段集合，根据源对象，生成只包含对应字段集合的目标对象
         */
        Object data = body.getData();
        //将data转为jsonObject，然后去除excludedFieldSet中的字段
        Object o = JSON.toJSON(data);
        if (o instanceof JSONObject) {
            HashMap<Object,Object> target = new HashMap<>();
            JSONObject jsonObject = (JSONObject) o;
            for (String s : includedFieldSet) {
                target.put(s,jsonObject.get(s));
            }
            o = target;
        } else if (o instanceof JSONArray) {
            JSONArray jsonArray = (JSONArray) o;
            for (int i = 0; i < jsonArray.size(); i++) {
                JSONObject source = (JSONObject)jsonArray.get(i);
                HashMap<Object,Object> target = new HashMap<>();
                for (String s : includedFieldSet) {
                    target.put(s,source.get(s));
                }
                jsonArray.set(i,target);
            }
        }

        /**
         * 5、偷梁换柱
         */
        body.setData(o);

        return body;
    }

    private HashSet<String> getFieldsShouldInclude(String activeFastJsonProfileInController, Class<?> clazz) {
        HashSet<String> includedFieldSet = new HashSet<>();

        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            /**
             * 获取field上的FastJsonFieldProfile注解，然后检查注解上的profile，看看该profile是否
             * 匹配看看该field是否需要被加入
             */
            FastJsonFieldProfile[] annotationsByType = declaredField.getAnnotationsByType(FastJsonFieldProfile.class);
            if (annotationsByType == null || annotationsByType.length == 0) {
                continue;
            }

            fieldLoop:
            for (FastJsonFieldProfile fastJsonFieldProfile : annotationsByType) {
                if (fastJsonFieldProfile.profileType() != FastJsonFieldProfileType.INCLUDE) {
                    continue;
                }

                for (String profile : fastJsonFieldProfile.profiles()) {
                    if (Objects.equals(profile, activeFastJsonProfileInController)) {

                        includedFieldSet.add(declaredField.getName());
                        continue fieldLoop;
                    }
                }
            }
        }

        return includedFieldSet;

    }

    /**
     * 获取指定class中，被指定的profile激活的field集合
     * @param activeFastJsonProfileInController
     * @param clazz
     * @return
     */
    private HashSet<String> getFieldsShouldExclude(String activeFastJsonProfileInController, Class<?> clazz) {
        HashSet<String> excludedFieldSet = new HashSet<>();

        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            /**
             * 获取field上的FastJsonFieldProfile注解，然后检查注解上的profile，看看该profile是否
             * 匹配看看该field是否需要被排除
             */
            FastJsonFieldProfile[] annotationsByType = declaredField.getAnnotationsByType(FastJsonFieldProfile.class);
            if (annotationsByType == null || annotationsByType.length == 0) {
                continue;
            }

            fieldLoop:
            for (FastJsonFieldProfile fastJsonFieldProfile : annotationsByType) {
                if (fastJsonFieldProfile.profileType() != FastJsonFieldProfileType.EXCLUDE) {
                    continue;
                }

                for (String profile : fastJsonFieldProfile.profiles()) {
                    if (Objects.equals(profile, activeFastJsonProfileInController)) {

                        excludedFieldSet.add(declaredField.getName());
                        continue fieldLoop;
                    }
                }
            }
        }

        return excludedFieldSet;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

        this.applicationContext = applicationContext;
    }
}
